<!DOCTYPE html><html lang="zh-CN" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0,viewport-fit=cover"><title>全栈开发(Spring Security) | 静待花开</title><meta name="author" content="brad 随风起舞"><meta name="copyright" content="brad 随风起舞"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta name="description" content="在这篇博文中,你将了解保护REST API，以便只有经过身份验证和授权的用户才能调用REST API并执行不同的CRUD操作。您将使用Spring Security来保护RESTful服务。首先，我将介绍Spring Security并解释所需的Maven依赖关系。然后，您将学习如何实现Spring Security来保护UserRegistrationSystem应用程序中的REST API。您">
<meta property="og:type" content="article">
<meta property="og:title" content="全栈开发(Spring Security)">
<meta property="og:url" content="https://zhangbo4881820.gitee.io/myblog/posts/20230/index.html">
<meta property="og:site_name" content="静待花开">
<meta property="og:description" content="在这篇博文中,你将了解保护REST API，以便只有经过身份验证和授权的用户才能调用REST API并执行不同的CRUD操作。您将使用Spring Security来保护RESTful服务。首先，我将介绍Spring Security并解释所需的Maven依赖关系。然后，您将学习如何实现Spring Security来保护UserRegistrationSystem应用程序中的REST API。您">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://i.loli.net/2021/02/24/5O1day2nriDzjSu.png">
<meta property="article:published_time" content="2019-11-04T02:32:02.000Z">
<meta property="article:modified_time" content="2023-09-07T09:44:01.884Z">
<meta property="article:author" content="brad 随风起舞">
<meta property="article:tag" content="实战">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://i.loli.net/2021/02/24/5O1day2nriDzjSu.png"><link rel="shortcut icon" href="/myblog/img/favicon.png"><link rel="canonical" href="https://zhangbo4881820.gitee.io/myblog/posts/20230/index.html"><link rel="preconnect" href="//cdn.jsdelivr.net"/><link rel="preconnect" href="//busuanzi.ibruce.info"/><link rel="stylesheet" href="/myblog/css/index.css"><link rel="stylesheet" href="/myblog/css/all.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="/myblog/css/fancybox.css" media="print" onload="this.media='all'"><script>const GLOBAL_CONFIG = {
  root: '/myblog/',
  algolia: undefined,
  localSearch: {"path":"/myblog/search.xml","preload":false,"top_n_per_article":1,"unescape":false,"languages":{"hits_empty":"找不到您查询的内容：${query}","hits_stats":"共找到 ${hits} 篇文章"}},
  translate: undefined,
  noticeOutdate: undefined,
  highlight: {"plugin":"highlighjs","highlightCopy":true,"highlightLang":true,"highlightHeightLimit":false},
  copy: {
    success: '复制成功',
    error: '复制错误',
    noSupport: '浏览器不支持'
  },
  relativeDate: {
    homepage: false,
    post: false
  },
  runtime: '',
  dateSuffix: {
    just: '刚刚',
    min: '分钟前',
    hour: '小时前',
    day: '天前',
    month: '个月前'
  },
  copyright: undefined,
  lightbox: 'fancybox',
  Snackbar: undefined,
  source: {
    justifiedGallery: {
      js: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery/dist/fjGallery.min.js',
      css: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery/dist/fjGallery.min.css'
    }
  },
  isPhotoFigcaption: false,
  islazyload: false,
  isAnchor: false,
  percent: {
    toc: true,
    rightside: false,
  },
  autoDarkmode: false
}</script><script id="config-diff">var GLOBAL_CONFIG_SITE = {
  title: '全栈开发(Spring Security)',
  isPost: true,
  isHome: false,
  isHighlightShrink: true,
  isToc: true,
  postUpdate: '2023-09-07 17:44:01'
}</script><noscript><style type="text/css">
  #nav {
    opacity: 1
  }
  .justified-gallery img {
    opacity: 1
  }

  #recent-posts time,
  #post-meta time {
    display: inline !important
  }
</style></noscript><script>(win=>{
    win.saveToLocal = {
      set: function setWithExpiry(key, value, ttl) {
        if (ttl === 0) return
        const now = new Date()
        const expiryDay = ttl * 86400000
        const item = {
          value: value,
          expiry: now.getTime() + expiryDay,
        }
        localStorage.setItem(key, JSON.stringify(item))
      },

      get: function getWithExpiry(key) {
        const itemStr = localStorage.getItem(key)

        if (!itemStr) {
          return undefined
        }
        const item = JSON.parse(itemStr)
        const now = new Date()

        if (now.getTime() > item.expiry) {
          localStorage.removeItem(key)
          return undefined
        }
        return item.value
      }
    }
  
    win.getScript = url => new Promise((resolve, reject) => {
      const script = document.createElement('script')
      script.src = url
      script.async = true
      script.onerror = reject
      script.onload = script.onreadystatechange = function() {
        const loadState = this.readyState
        if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
        script.onload = script.onreadystatechange = null
        resolve()
      }
      document.head.appendChild(script)
    })
  
    win.getCSS = (url,id = false) => new Promise((resolve, reject) => {
      const link = document.createElement('link')
      link.rel = 'stylesheet'
      link.href = url
      if (id) link.id = id
      link.onerror = reject
      link.onload = link.onreadystatechange = function() {
        const loadState = this.readyState
        if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
        link.onload = link.onreadystatechange = null
        resolve()
      }
      document.head.appendChild(link)
    })
  
      win.activateDarkMode = function () {
        document.documentElement.setAttribute('data-theme', 'dark')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d')
        }
      }
      win.activateLightMode = function () {
        document.documentElement.setAttribute('data-theme', 'light')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff')
        }
      }
      const t = saveToLocal.get('theme')
    
          if (t === 'dark') activateDarkMode()
          else if (t === 'light') activateLightMode()
        
      const asideStatus = saveToLocal.get('aside-status')
      if (asideStatus !== undefined) {
        if (asideStatus === 'hide') {
          document.documentElement.classList.add('hide-aside')
        } else {
          document.documentElement.classList.remove('hide-aside')
        }
      }
    
    const detectApple = () => {
      if(/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)){
        document.documentElement.classList.add('apple')
      }
    }
    detectApple()
    })(window)</script><meta name="generator" content="Hexo 7.0.0-rc2"><link rel="alternate" href="/myblog/atom.xml" title="静待花开" type="application/atom+xml">
</head><body><div id="sidebar"><div id="menu-mask"></div><div id="sidebar-menus"><div class="avatar-img is-center"><img src="https://i.loli.net/2021/02/24/5O1day2nriDzjSu.png" onerror="onerror=null;src='/img/friend_404.gif'" alt="avatar"/></div><div class="sidebar-site-data site-data is-center"><a href="/myblog/archives/"><div class="headline">文章</div><div class="length-num">57</div></a><a href="/myblog/tags/"><div class="headline">标签</div><div class="length-num">20</div></a><a href="/myblog/categories/"><div class="headline">分类</div><div class="length-num">25</div></a></div><hr class="custom-hr"/><div class="menus_items"><div class="menus_item"><a class="site-page" href="/myblog/"><i class="fa-fw fas fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page" href="/myblog/archives/"><i class="fa-fw fas fa-archive"></i><span> 时间轴</span></a></div><div class="menus_item"><a class="site-page" href="/myblog/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></div><div class="menus_item"><a class="site-page" href="/myblog/categories/"><i class="fa-fw fas fa-folder-open"></i><span> 分类</span></a></div><div class="menus_item"><a class="site-page group" href="javascript:void(0);"><i class="fa-fw fas fa-list"></i><span> 分享</span><i class="fas fa-chevron-down"></i></a><ul class="menus_item_child"><li><a class="site-page child" href="/myblog/Gallery/"><i class="fa-fw fas fa-images"></i><span> 照片</span></a></li><li><a class="site-page child" href="/myblog/music/"><i class="fa-fw fas fa-music"></i><span> 音乐</span></a></li><li><a class="site-page child" href="/myblog/movies/"><i class="fa-fw fas fa-video"></i><span> 视频</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="/myblog/link/"><i class="fa-fw fas fa-link"></i><span> 友链</span></a></div><div class="menus_item"><a class="site-page" href="/myblog/about/"><i class="fa-fw fas fa-heart"></i><span> 关于</span></a></div></div></div></div><div class="post" id="body-wrap"><header class="not-top-img" id="page-header"><nav id="nav"><span id="blog-info"><a href="/myblog/" title="静待花开"><span class="site-name">静待花开</span></a></span><div id="menus"><div id="search-button"><a class="site-page social-icon search" href="javascript:void(0);"><i class="fas fa-search fa-fw"></i><span> 搜索</span></a></div><div class="menus_items"><div class="menus_item"><a class="site-page" href="/myblog/"><i class="fa-fw fas fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page" href="/myblog/archives/"><i class="fa-fw fas fa-archive"></i><span> 时间轴</span></a></div><div class="menus_item"><a class="site-page" href="/myblog/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></div><div class="menus_item"><a class="site-page" href="/myblog/categories/"><i class="fa-fw fas fa-folder-open"></i><span> 分类</span></a></div><div class="menus_item"><a class="site-page group" href="javascript:void(0);"><i class="fa-fw fas fa-list"></i><span> 分享</span><i class="fas fa-chevron-down"></i></a><ul class="menus_item_child"><li><a class="site-page child" href="/myblog/Gallery/"><i class="fa-fw fas fa-images"></i><span> 照片</span></a></li><li><a class="site-page child" href="/myblog/music/"><i class="fa-fw fas fa-music"></i><span> 音乐</span></a></li><li><a class="site-page child" href="/myblog/movies/"><i class="fa-fw fas fa-video"></i><span> 视频</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="/myblog/link/"><i class="fa-fw fas fa-link"></i><span> 友链</span></a></div><div class="menus_item"><a class="site-page" href="/myblog/about/"><i class="fa-fw fas fa-heart"></i><span> 关于</span></a></div></div><div id="toggle-menu"><a class="site-page" href="javascript:void(0);"><i class="fas fa-bars fa-fw"></i></a></div></div></nav></header><main class="layout" id="content-inner"><div id="post"><div id="post-info"><h1 class="post-title">全栈开发(Spring Security)</h1><div id="post-meta"><div class="meta-firstline"><span class="post-meta-date"><i class="far fa-calendar-alt fa-fw post-meta-icon"></i><span class="post-meta-label">发表于</span><time class="post-meta-date-created" datetime="2019-11-04T02:32:02.000Z" title="发表于 2019-11-04 10:32:02">2019-11-04</time><span class="post-meta-separator">|</span><i class="fas fa-history fa-fw post-meta-icon"></i><span class="post-meta-label">更新于</span><time class="post-meta-date-updated" datetime="2023-09-07T09:44:01.884Z" title="更新于 2023-09-07 17:44:01">2023-09-07</time></span><span class="post-meta-categories"><span class="post-meta-separator">|</span><i class="fas fa-inbox fa-fw post-meta-icon"></i><a class="post-meta-categories" href="/myblog/categories/%E5%AE%9E%E6%88%98/">实战</a></span></div><div class="meta-secondline"><span class="post-meta-separator">|</span><span class="post-meta-pv-cv" id="" data-flag-title="全栈开发(Spring Security)"><i class="far fa-eye fa-fw post-meta-icon"></i><span class="post-meta-label">阅读量:</span><span id="busuanzi_value_page_pv"><i class="fa-solid fa-spinner fa-spin"></i></span></span></div></div></div><article class="post-content" id="article-container"><p>在这篇博文中,你将了解保护REST API，以便只有经过身份验证和授权的用户才能调用REST API并执行不同的CRUD操作。<br>您将使用Spring Security来保护RESTful服务。首先，我将介绍Spring Security并解释所需的Maven依赖关系。<br>然后，您将学习如何实现Spring Security来保护UserRegistrationSystem应用程序中的REST API。<br>您还将了解有关内存和数据库配置的信息，以设置用户凭据及其角色。</p>
<h1 id="Spring-Security-的介绍"><a href="#Spring-Security-的介绍" class="headerlink" title="Spring Security 的介绍"></a>Spring Security 的介绍</h1><p>Security是一种保护资源免遭未经身份验证和未经授权的用户访问,并允许特定(经过身份验证和授权的)用户访问这些受保护资源的过程.<br>安全性不同于防火墙，入侵检测，JVM安全性或其他任何方面。Spring Security主要针对基于Spring的应用程序</p>
<p>Spring Security框架最初是Acegi Security框架，后来被Spring用作子项目。它已成为保护使用Spring Framework开发的应用程序的事实上的标准。Spring Security在HTTP请求方法的调用级别支持身份验证和授权</p>
<h2 id="Authentication-身份验证-and-Authorization-授权"><a href="#Authentication-身份验证-and-Authorization-授权" class="headerlink" title="Authentication(身份验证) and Authorization(授权)"></a>Authentication(身份验证) and Authorization(授权)</h2><p>身份验证和授权是Spring Security 提供的两个主要操作。</p>
<p>身份验证是验证用户是否是该用户声称的身份的过程。身份验证通过识别和验证进行。授权是为经过身份验证的用户授予对资源的访问权限的过程。换句话说，它为经过身份验证的用户提供访问控制。<br>让我们看一个UserRegistrationSystem应用程序中的示例：用户USER可以执行用户注册，更新用户详细信息并获取用户列表，而只有用<br>户ADMIN才具有删除用户的额外特权。授予客户端的访问权限将确定该应用程序的访问规则。<br>Spring Security支持基于URL的安全性，并使用过滤器实现。<br>Spring Security还支持方法级安全性，其中仅允许授权用户调用授予他们的特定方法</p>
<h2 id="介绍基本身份验证"><a href="#介绍基本身份验证" class="headerlink" title="介绍基本身份验证"></a>介绍基本身份验证</h2><p>传统的身份验证方法,例如使用登录页面和会话标识绑定到需要人工交互的基于Web的客户端。<br>当与REST客户端,甚至可能不是基于Web的应用程序进行通信时，您需要考虑基本身份验证提供的解决方案</p>
<p>基本身份验证是一个标准的HTTP标头（授权）,该标头随每个请求一起发送Base64编码的凭据。Base64不会以任何方式加密或散列；<br>换句话说，用户名和密码以明文格式编码</p>
<p>凭证字符串包含用户名和密码，格式为username：password。下面展示表头的算法代码.</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">String plain_Client_Credentials=&quot;user:password&quot;; </span><br><span class="line">String base64_ClientCredentials = new String(Base64.encodeBase64(plain_Client_Credentials.getBytes())); HttpHeaders headers = getHeaders(); </span><br><span class="line">headers.add(&quot;Authorization&quot;, &quot;Basic &quot; + base64_ClientCredentials);</span><br></pre></td></tr></table></figure>
<p>上面的代码将会生成一个header授权: Basic dXNlcjpwYXNzd29yZA&#x3D;&#x3D;,在每个http request中都会发送这个授权码.</p>
<p>基本身份验证是保护RESTful API的最简单的技术之一，因为它不需要cookie，会话标识符甚至登录页面.</p>
<h2 id="BasicAuthenticationFilter-基本验证过滤器"><a href="#BasicAuthenticationFilter-基本验证过滤器" class="headerlink" title="BasicAuthenticationFilter(基本验证过滤器)"></a>BasicAuthenticationFilter(基本验证过滤器)</h2><p>BasicAuthenticationFilter对象负责处理任何HTTP请求，该HTTP请求包含Authorization的HTTP请求标头和基本身份验证的身份验证模式，以及Base64编码的username：password令牌</p>
<p>如果身份验证成功，则Spring中的BasicAuthenticationFilter负责将结果（身份验证对象）放入SecurityContextHolder中。</p>
<h1 id="在RESTful-Services中启用Spring-Security"><a href="#在RESTful-Services中启用Spring-Security" class="headerlink" title="在RESTful Services中启用Spring Security"></a>在RESTful Services中启用Spring Security</h1><h2 id="什么是Spring-Boot的安全启动器"><a href="#什么是Spring-Boot的安全启动器" class="headerlink" title="什么是Spring Boot的安全启动器"></a>什么是Spring Boot的安全启动器</h2><p>Spring Boot的目标是简化应用程序开发。与Spring Boot的所有其他功能一样，通过添加匹配的启动程序POM，称为Security的<br>Spring Boot启动程序为开发人员创建了基本配置设置，包括HTTP Basic Authentication和<br>AuthenticationManager bean，它们在Spring Boot应用程序上构建时具有内存中的默认用户</p>
<p>现在开始在UserRegistrationSystem应用中配置 Spring Security.</p>
<h2 id="添加Spring-Security依赖"><a href="#添加Spring-Security依赖" class="headerlink" title="添加Spring Security依赖"></a>添加Spring Security依赖</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">&lt;dependency&gt; </span><br><span class="line">&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt; </span><br><span class="line">&lt;artifactId&gt;spring-boot-starter-security&lt;/artifactId&gt; </span><br><span class="line">&lt;/dependency&gt;</span><br></pre></td></tr></table></figure>

<p>一旦你在你的pom.xml文件中添加这个依赖,那么你整个应用会通过HTTP的Basic Authentication(基本身份验证)去保护除了静态文件(CSS,javascript,等等)的所有资源,还有一个在内存中带有默认用户的AuthenticationManager bean将会在应用中创建</p>
<p>默认用户名字是user,当你启动应用程序密码会生成并在控制台打印出来.这个时候登陆访问API的时候就会要求输入用户名和密码了</p>
<p>如果觉得在控制太拷贝密码太烦,可以通过配置文件application.properties去配置用户名密码.覆盖默认的值</p>
<p><img src="/myblog/../../../../image/spring-security-config.png"></p>
<h1 id="自定义用户验证"><a href="#自定义用户验证" class="headerlink" title="自定义用户验证"></a>自定义用户验证</h1><p>用户需要被验证,然后在被允许访问受保护的资源前进行授权.在SpringSecurity中authentication验证器提供对用户的验证,验证成功的用户可以访问系统的受保护的资源.</p>
<p>Spring Security支持多种方式对用户进行验证,例如:使用内存中的定义验证,存储用户细节的关系型数据库验证<br>通过配置用户和角色来定制用户验证.</p>
<ul>
<li><p>有两个角色:<strong>USER</strong>和<strong>ADMIN</strong></p>
</li>
<li><p>通过凭据 user/password 来创建USER角色</p>
</li>
<li><p>通过凭据 admin/password 来创建AMIN角色</p>
</li>
</ul>
<p>在系统中,指定用户角色来访问不同的REST端点</p>
<table>
<thead>
<tr>
<th>User Role</th>
<th>HTTP Method</th>
<th>API Endpoints</th>
</tr>
</thead>
<tbody><tr>
<td>USER</td>
<td>GET</td>
<td>/api/user/</td>
</tr>
<tr>
<td>USER</td>
<td>POST</td>
<td>/api/user/</td>
</tr>
<tr>
<td>USER</td>
<td>PUT</td>
<td>/api/user/{id}</td>
</tr>
<tr>
<td>ADMIN</td>
<td>DELETE</td>
<td>/api/user/{id}</td>
</tr>
</tbody></table>
<h2 id="内存定义中的自定义验证"><a href="#内存定义中的自定义验证" class="headerlink" title="内存定义中的自定义验证"></a>内存定义中的自定义验证</h2><p>如果应用程序只有少量的用户,你应该在Spring的配置文件中定义用户细节去代替从持久引擎中查询数据库用户信息以便用户细节能被加载到内存中</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">@Configuration</span><br><span class="line">@Order(SecurityProperties.BASIC_AUTH_ORDER)</span><br><span class="line">public class SpringSecurityConfiguration_InMemory extends WebSecurityConfigurerAdapter &#123;</span><br><span class="line"></span><br><span class="line">    /**</span><br><span class="line">     *</span><br><span class="line">     * @param auth</span><br><span class="line">     * @throws Exception</span><br><span class="line">     * 在内存中添加用户密码角色</span><br><span class="line">     */</span><br><span class="line">    @Autowired</span><br><span class="line">    protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception &#123;</span><br><span class="line"></span><br><span class="line">        auth.inMemoryAuthentication()</span><br><span class="line">                .passwordEncoder(new BCryptPasswordEncoder()).withUser(&quot;user&quot;)</span><br><span class="line">                .password(new BCryptPasswordEncoder().encode(&quot;password&quot;)).roles(&quot;USER&quot;);</span><br><span class="line"></span><br><span class="line">        auth.inMemoryAuthentication()</span><br><span class="line">                .passwordEncoder(new BCryptPasswordEncoder()).withUser(&quot;admin&quot;)</span><br><span class="line">                .password(new BCryptPasswordEncoder().encode(&quot;password&quot;)).roles(&quot;ADMIN&quot;);</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    /**</span><br><span class="line">     *</span><br><span class="line">     * @param http</span><br><span class="line">     * @throws Exception</span><br><span class="line">     * 配置REST API 访问的指定角色 并且不允许跨域</span><br><span class="line">     */</span><br><span class="line">    @Override</span><br><span class="line">    protected void configure(HttpSecurity http) throws Exception &#123;</span><br><span class="line"></span><br><span class="line">        http.httpBasic().and()</span><br><span class="line">                .authorizeRequests()</span><br><span class="line">                .antMatchers(HttpMethod.GET,&quot;/api/user/&quot;)</span><br><span class="line">                .hasRole(&quot;USER&quot;)</span><br><span class="line">                .antMatchers(HttpMethod.POST,&quot;/api/user/&quot;)</span><br><span class="line">                .hasRole(&quot;USER&quot;)</span><br><span class="line">                .antMatchers(HttpMethod.PUT,&quot;/api/user/**&quot;)</span><br><span class="line">                .hasRole(&quot;USER&quot;)</span><br><span class="line">                .antMatchers(HttpMethod.DELETE,&quot;/api/user/**&quot;)</span><br><span class="line">                .hasRole(&quot;ADMIN&quot;)</span><br><span class="line">                .and().cors().disable();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>在上面的代码片段中,创建了一个SpringSecurityConfiguration_InMemory的类,<br>并且用@Configuration注解这个类,让这个类成为一个注解类.让这个类继承WebSecurityConfigurerAdapter类,这会允许你去配置Spring Secutiry 和为你的应用重写默认方法.</p>
<p>用两个用户和角色和密码去配置验证,admin用户有两个角色 <strong>USER,ADMIN</strong>,user只有一个USER角色.<br>在pring secutiry5中需要给密码加密,使用BCrypt加密方式.<br>使用内存中的定义验证ConfigurationGlobal方法去配置内存验证.使用@autowired注解这个方法,<br>这样方法的参数AuthenticationManagerBuilder就会被注入进来.</p>
<p>重写configure方法,这个方法带有HttpSecurity参数.在这个方法中通过角色到URL路径的映射来配置验证.使用HTTP Basic验证<br>去验证每一个请求.使用antMatchers方法将HttpMethod和url模板路径映射到指定的角色上.<br>最后还进行跨站点访问的限制.因为默认跨域访问时激活的CsrfFilter会拦截请求.但是在拦截器里排除了post等方法.看下面源码</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">private static final class DefaultRequiresCsrfMatcher implements RequestMatcher &#123;</span><br><span class="line">	private final HashSet&lt;String&gt; allowedMethods = new HashSet&lt;&gt;(</span><br><span class="line">			Arrays.asList(&quot;GET&quot;, &quot;HEAD&quot;, &quot;TRACE&quot;, &quot;OPTIONS&quot;));</span><br><span class="line"></span><br><span class="line">	/*</span><br><span class="line">	 * (non-Javadoc)</span><br><span class="line">	 *</span><br><span class="line">	 * @see</span><br><span class="line">	 * org.springframework.security.web.util.matcher.RequestMatcher#matches(javax.</span><br><span class="line">	 * servlet.http.HttpServletRequest)</span><br><span class="line">	 */</span><br><span class="line">	@Override</span><br><span class="line">	public boolean matches(HttpServletRequest request) &#123;</span><br><span class="line">		return !this.allowedMethods.contains(request.getMethod());</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="关系数据库的自定义验证"><a href="#关系数据库的自定义验证" class="headerlink" title="关系数据库的自定义验证"></a>关系数据库的自定义验证</h2><p>先来创建一个 UserInfo 实体类,他表示这个系统的用户,这个类包含:ID,username,password,isEnabled,role.</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">@Entity</span><br><span class="line">@Table(name = &quot;users&quot;)</span><br><span class="line">@Data</span><br><span class="line">public class UserInfo &#123;</span><br><span class="line"></span><br><span class="line">    @Id</span><br><span class="line">    @GeneratedValue</span><br><span class="line">    @Column(name = &quot;userid&quot;)</span><br><span class="line">    private Long id;</span><br><span class="line"></span><br><span class="line">    @Column(name = &quot;username&quot;)</span><br><span class="line">    @NotEmpty</span><br><span class="line">    private String username;</span><br><span class="line"></span><br><span class="line">    @Column(name = &quot;password&quot;)</span><br><span class="line">    @NotEmpty</span><br><span class="line">    private String password;</span><br><span class="line"></span><br><span class="line">    @Column(name = &quot;enabled&quot;)</span><br><span class="line">    private boolean isEnabled;</span><br><span class="line"></span><br><span class="line">    @Column(name = &quot;role&quot;)</span><br><span class="line">    private String role;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>想要在数据库中存储系统用户,那么需要一个UserInfoJpaRepository接口来对UserInfo实体操作CRUD,下面	代码中继承了JpaRepository.那么就有了他的默认方法,另外还自定义了一个方法findByUsername用username做参数<br>Spring DATA JPA将会在运行时提供一个实现.</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">/**</span><br><span class="line"> * create by zhangbo on 2019/11/4 0004</span><br><span class="line"> */</span><br><span class="line">public interface UserInfoJpaRepository extends JpaRepository&lt;UserInfo, Long&gt; &#123;</span><br><span class="line"></span><br><span class="line">    public UserInfo findByUsername(String username);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>简单起见,已经生成一些测试用户,在resources文件夹下创建import.sql文件,然后将如下代码放进这个文件,那么启动时候<br>hibernate就会自动更新这些测试用户.让应用程序能够使用他们.</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">INSERT INTO users (username, password, enabled, role) VALUES (&#x27;user&#x27;, &#x27;password&#x27;, true, &#x27;USER&#x27;);</span><br><span class="line">INSERT INTO users (username, password, enabled, role) VALUES (&#x27;admin&#x27;, &#x27;password&#x27;, true, &#x27;ADMIN&#x27;);</span><br><span class="line">INSERT INTO users (username, password, enabled, role) VALUES (&#x27;ravi&#x27;, &#x27;password&#x27;, true, &#x27;USER&#x27;);</span><br></pre></td></tr></table></figure>

<h3 id="UserDetailsService-的实现"><a href="#UserDetailsService-的实现" class="headerlink" title="UserDetailsService 的实现"></a>UserDetailsService 的实现</h3><p>在Spring Security中,UserDetailsService用于从后端返回用户信息,例如:在验证程序中,一个已经提交的用户凭证和数据库的<br>用户数据做比较.</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">@Service</span><br><span class="line">public class UserInfoDetailService implements UserDetailsService &#123;</span><br><span class="line"></span><br><span class="line">    @Autowired</span><br><span class="line">    private UserInfoJpaRepository userInfoJpaRepository;</span><br><span class="line"></span><br><span class="line">    @Override</span><br><span class="line">    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException &#123;</span><br><span class="line"></span><br><span class="line">        UserInfo user = userInfoJpaRepository.findByUsername(username);</span><br><span class="line">        if (user == null) &#123;</span><br><span class="line">            throw new UsernameNotFoundException(&quot;用户名找不到&quot;+username);</span><br><span class="line">        &#125;</span><br><span class="line">        return new User(user.getUsername(),user.getPassword(),getAuthorities(user));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    private Collection&lt;GrantedAuthority&gt; getAuthorities(UserInfo user) &#123;</span><br><span class="line"></span><br><span class="line">        List&lt;GrantedAuthority&gt; authorities = new ArrayList&lt;&gt;();</span><br><span class="line">        authorities = AuthorityUtils.createAuthorityList(user.getRole());</span><br><span class="line">        return authorities;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>UserInfoDetailService 这个类注入了UserInfoJpaRepository去查询数据库获取UserInfo.这个类重写了<br>loadUserByUsername这个方法返回一个userdetails类型的User实例,这方法首先检查从数据库获取的是否为null,如果是那么抛出适当的异常信息,如果不是那么创建org.springframework.security.core.userdetails.User的实例,并且使用数据库返回的数据填充User实例并返回</p>
<h3 id="自定义-Spring-Security-和-安全的URI"><a href="#自定义-Spring-Security-和-安全的URI" class="headerlink" title="自定义 Spring Security 和 安全的URI"></a>自定义 Spring Security 和 安全的URI</h3><p>接下来通过创建一个SpringSecurityConfiguration_Database配置类来自定义spring Security的默认行为,和安全的URI.<br>这个配置类使用@configuration和@EnableWebSecurity注解,并且继承WebSecurityConfigurerAdapter类,这个类提供一些帮助配置Spring Security的方法.</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">/**</span><br><span class="line"> * create by zhangbo on 2019/11/4 0004</span><br><span class="line"> */</span><br><span class="line">@Configuration</span><br><span class="line">@EnableWebSecurity</span><br><span class="line">public class SpringSecurityConfiguration_Database extends WebSecurityConfigurerAdapter &#123;</span><br><span class="line"></span><br><span class="line">    @Autowired</span><br><span class="line">    private UserInfoDetailService userInfoDetailService;</span><br><span class="line"></span><br><span class="line">    @Override</span><br><span class="line">    protected void configure(AuthenticationManagerBuilder auth) throws Exception &#123;</span><br><span class="line">        auth.userDetailsService(userInfoDetailService)</span><br><span class="line">                .passwordEncoder(new BCryptPasswordEncoder());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    @Override</span><br><span class="line">    protected void configure(HttpSecurity http) throws Exception &#123;</span><br><span class="line"></span><br><span class="line">        http.sessionManagement()</span><br><span class="line">                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)</span><br><span class="line">                .and()</span><br><span class="line">                .authorizeRequests()</span><br><span class="line">                .antMatchers(&quot;/api/user/**&quot;)</span><br><span class="line">                .authenticated()</span><br><span class="line">                .and()</span><br><span class="line">                .httpBasic()</span><br><span class="line">                .realmName(&quot;User Registration System&quot;)</span><br><span class="line">                .and()</span><br><span class="line">                .csrf()</span><br><span class="line">                .disable();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>SpringSecurityConfiguration_Database这个配置类注入了UserInfoDetailService bean.重写了带有参数<br>AuthenticationManagerBuilder的configure方法.AuthenticationManagerBuilder这是一个实现建造者模式,提供组装成<br>AuthenticationManager的一种方式的帮助类.<br>我们已经使用他把userInfoDetailService加入到实例中.同时还要传个加密对象BCryptPasswordEncoder.5版本的特性<br>也重写了带有参数HttpSecurity的configure方法.这对配置HTTP Basic Authentication有帮助<br>这个配置使的所有的端点和访问权限很安全.HttpSecurity这个参数允许你指定一些受保护的URI和不受保护的URI<br>这个方法的实现是,不创建HTTP session也不存储已经登陆的用户的SecurityContext的值存入session,<br>使用SessionCreationPolicy.STATELESS的创建策略实现的.<br>然后，您使用了antMatchers来提供您希望Spring Security使用经过身份验证的方法保护的Ant样式的URI表达式，该方法允许经过身份验证的用户访问相应的端点。<br>最后，您启用了HTTP基本身份验证，并将领域名称设置为“User Registration System”。</p>
<h3 id="方法级别的保护"><a href="#方法级别的保护" class="headerlink" title="方法级别的保护"></a>方法级别的保护</h3><p>方法级安全性是安全URI访问的替代方法。有时需要确保只有具有管理权限的用户（具有admin角色）才能删除UserRegistrationSystem中的注册用户，这可以通过对方法实施细粒度的安全控制来实现。<br>您将在deleteUser方法上应用Spring Security的方法级安全性。</p>
<pre><code>@Configuration
@EnableWebSecurity
**@EnableGlobalMethodSecurity(prePostEnabled = true)**
public class SpringSecurityConfiguration_Database extends WebSecurityConfigurerAdapter &#123;
</code></pre>
<p>Spring Security支持一组丰富的方法级授权注释以及类级授权注释。您在EnableGlobalMethodSecurity中将prePostEnabled标志设置为true,这将使Spring Security批注能够执行方法前和方法后的授权检查。<br>因此，在需要方法级别验证的方法上使用@PreAuthorize批注</p>
<pre><code>@DeleteMapping(value = &quot;/&#123;id&#125;&quot;)
**@PreAuthorize(&quot;hasRole(&#39;ADMIN&#39;)&quot;)**
public ResponseEntity&lt;UserDTO&gt; deleteUser(@PathVariable final Long id) &#123;
</code></pre>
<p>@PreAuthorize 注解是检查已经验证过的或者登陆的用户是否有ADMIN的权限.只有验证通过了才能访问该方法.</p>
<p>然后就是springsecurity5会在hasRole校色验证里面添加一个默认的前缀ROLE_,如果想要去掉这个默认前缀.那么单独写个配置类</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">package com.zhang.myproject.config;</span><br><span class="line"></span><br><span class="line">import org.springframework.context.annotation.Bean;</span><br><span class="line">import org.springframework.context.annotation.Configuration;</span><br><span class="line">import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;</span><br><span class="line">import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;</span><br><span class="line"></span><br><span class="line">/**</span><br><span class="line"> * create by zhangbo on 2019/11/5 0005</span><br><span class="line"> */</span><br><span class="line">@Configuration</span><br><span class="line">public class myconfig &#123;</span><br><span class="line"></span><br><span class="line">    @Bean</span><br><span class="line">    public DefaultMethodSecurityExpressionHandler defaultMethodSecurityExpressionHandler() &#123;</span><br><span class="line">        DefaultMethodSecurityExpressionHandler defaultMethodSecurityExpressionHandler = new DefaultMethodSecurityExpressionHandler();</span><br><span class="line">        defaultMethodSecurityExpressionHandler.setDefaultRolePrefix(&quot;&quot;);</span><br><span class="line">        return defaultMethodSecurityExpressionHandler;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    @Bean</span><br><span class="line">    public DefaultWebSecurityExpressionHandler defaultWebSecurityExpressionHandler() &#123;</span><br><span class="line">        DefaultWebSecurityExpressionHandler defaultWebSecurityExpressionHandler = new DefaultWebSecurityExpressionHandler();</span><br><span class="line">        defaultWebSecurityExpressionHandler.setDefaultRolePrefix(&quot;&quot;);</span><br><span class="line">        return defaultWebSecurityExpressionHandler;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>上面的配置类把方法级和Web级别的默认角色前缀都清除了.</p>
<h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>在这里简单理解了下spring security的验证和授权.通过实现基本验证方式,你就可以对RESTful API做权限认证.<br>然后自定义验证,在内存方面和数据库方面.</p>
</article><div class="post-copyright"><div class="post-copyright__author"><span class="post-copyright-meta">文章作者: </span><span class="post-copyright-info"><a href="https://zhangbo4881820.gitee.io/myblog">brad 随风起舞</a></span></div><div class="post-copyright__type"><span class="post-copyright-meta">文章链接: </span><span class="post-copyright-info"><a href="https://zhangbo4881820.gitee.io/myblog/posts/20230/">https://zhangbo4881820.gitee.io/myblog/posts/20230/</a></span></div><div class="post-copyright__notice"><span class="post-copyright-meta">版权声明: </span><span class="post-copyright-info">本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank">CC BY-NC-SA 4.0</a> 许可协议。转载请注明来自 <a href="https://zhangbo4881820.gitee.io/myblog" target="_blank">静待花开</a>！</span></div></div><div class="tag_share"><div class="post-meta__tag-list"><a class="post-meta__tags" href="/myblog/tags/%E5%AE%9E%E6%88%98/">实战</a></div><div class="post_share"><div class="social-share" data-image="https://i.loli.net/2021/02/24/5O1day2nriDzjSu.png" data-sites="facebook,twitter,wechat,weibo,qq"></div><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/social-share.js/1.0.16/css/share.min.css" media="print" onload="this.media='all'"><script src="https://cdnjs.cloudflare.com/ajax/libs/social-share.js/1.0.16/js/social-share.min.js" defer></script></div></div><nav class="pagination-post" id="pagination"><div class="prev-post pull-left"><a href="/myblog/posts/62645/" title="Springboot热加载"><div class="cover" style="background: var(--default-bg-color)"></div><div class="pagination-info"><div class="label">上一篇</div><div class="prev_info">Springboot热加载</div></div></a></div><div class="next-post pull-right"><a href="/myblog/posts/45226/" title="全栈开发(AngularJS原理)"><div class="cover" style="background: var(--default-bg-color)"></div><div class="pagination-info"><div class="label">下一篇</div><div class="next_info">全栈开发(AngularJS原理)</div></div></a></div></nav><div class="relatedPosts"><div class="headline"><i class="fas fa-thumbs-up fa-fw"></i><span>相关推荐</span></div><div class="relatedPosts-list"><div><a href="/myblog/posts/45226/" title="全栈开发(AngularJS原理)"><div class="cover" style="background: var(--default-bg-color)"></div><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2019-11-01</div><div class="title">全栈开发(AngularJS原理)</div></div></a></div><div><a href="/myblog/posts/21667/" title="全栈开发(RESTful和测试)"><div class="cover" style="background: var(--default-bg-color)"></div><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2019-11-06</div><div class="title">全栈开发(RESTful和测试)</div></div></a></div><div><a href="/myblog/posts/12008/" title="全栈开发(AngularJS)"><div class="cover" style="background: var(--default-bg-color)"></div><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2019-11-05</div><div class="title">全栈开发(AngularJS)</div></div></a></div><div><a href="/myblog/posts/45057/" title="全栈开发(Spring Boot Actuator)"><div class="cover" style="background: var(--default-bg-color)"></div><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2019-11-07</div><div class="title">全栈开发(Spring Boot Actuator)</div></div></a></div></div></div></div><div class="aside-content" id="aside-content"><div class="card-widget card-info"><div class="is-center"><div class="avatar-img"><img src="https://i.loli.net/2021/02/24/5O1day2nriDzjSu.png" onerror="this.onerror=null;this.src='/myblog/img/friend_404.gif'" alt="avatar"/></div><div class="author-info__name">brad 随风起舞</div><div class="author-info__description">有些事情需要一辈子的沉淀</div></div><div class="card-info-data site-data is-center"><a href="/myblog/archives/"><div class="headline">文章</div><div class="length-num">57</div></a><a href="/myblog/tags/"><div class="headline">标签</div><div class="length-num">20</div></a><a href="/myblog/categories/"><div class="headline">分类</div><div class="length-num">25</div></a></div><a id="card-info-btn" href="https://github.com/xxxxxx"><i class="fab fa-github"></i><span>Follow Me</span></a><div class="card-info-social-icons is-center"><a class="social-icon" href="https://github.com/xxxxx" target="_blank" title="Github"><i class="fab fa-github" style="color: #24292e;"></i></a><a class="social-icon" href="mailto:xxxxxx@gmail.com" target="_blank" title="Email"><i class="fas fa-envelope" style="color: #4a7dbe;"></i></a></div></div><div class="card-widget card-announcement"><div class="item-headline"><i class="fas fa-bullhorn fa-shake"></i><span>公告</span></div><div class="announcement_content">This is my Blog</div></div><div class="sticky_layout"><div class="card-widget" id="card-toc"><div class="item-headline"><i class="fas fa-stream"></i><span>目录</span><span class="toc-percentage"></span></div><div class="toc-content"><ol class="toc"><li class="toc-item toc-level-1"><a class="toc-link" href="#Spring-Security-%E7%9A%84%E4%BB%8B%E7%BB%8D"><span class="toc-number">1.</span> <span class="toc-text">Spring Security 的介绍</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#Authentication-%E8%BA%AB%E4%BB%BD%E9%AA%8C%E8%AF%81-and-Authorization-%E6%8E%88%E6%9D%83"><span class="toc-number">1.1.</span> <span class="toc-text">Authentication(身份验证) and Authorization(授权)</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E4%BB%8B%E7%BB%8D%E5%9F%BA%E6%9C%AC%E8%BA%AB%E4%BB%BD%E9%AA%8C%E8%AF%81"><span class="toc-number">1.2.</span> <span class="toc-text">介绍基本身份验证</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#BasicAuthenticationFilter-%E5%9F%BA%E6%9C%AC%E9%AA%8C%E8%AF%81%E8%BF%87%E6%BB%A4%E5%99%A8"><span class="toc-number">1.3.</span> <span class="toc-text">BasicAuthenticationFilter(基本验证过滤器)</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E5%9C%A8RESTful-Services%E4%B8%AD%E5%90%AF%E7%94%A8Spring-Security"><span class="toc-number">2.</span> <span class="toc-text">在RESTful Services中启用Spring Security</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E4%BB%80%E4%B9%88%E6%98%AFSpring-Boot%E7%9A%84%E5%AE%89%E5%85%A8%E5%90%AF%E5%8A%A8%E5%99%A8"><span class="toc-number">2.1.</span> <span class="toc-text">什么是Spring Boot的安全启动器</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%B7%BB%E5%8A%A0Spring-Security%E4%BE%9D%E8%B5%96"><span class="toc-number">2.2.</span> <span class="toc-text">添加Spring Security依赖</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E8%87%AA%E5%AE%9A%E4%B9%89%E7%94%A8%E6%88%B7%E9%AA%8C%E8%AF%81"><span class="toc-number">3.</span> <span class="toc-text">自定义用户验证</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%86%85%E5%AD%98%E5%AE%9A%E4%B9%89%E4%B8%AD%E7%9A%84%E8%87%AA%E5%AE%9A%E4%B9%89%E9%AA%8C%E8%AF%81"><span class="toc-number">3.1.</span> <span class="toc-text">内存定义中的自定义验证</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%85%B3%E7%B3%BB%E6%95%B0%E6%8D%AE%E5%BA%93%E7%9A%84%E8%87%AA%E5%AE%9A%E4%B9%89%E9%AA%8C%E8%AF%81"><span class="toc-number">3.2.</span> <span class="toc-text">关系数据库的自定义验证</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#UserDetailsService-%E7%9A%84%E5%AE%9E%E7%8E%B0"><span class="toc-number">3.2.1.</span> <span class="toc-text">UserDetailsService 的实现</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E8%87%AA%E5%AE%9A%E4%B9%89-Spring-Security-%E5%92%8C-%E5%AE%89%E5%85%A8%E7%9A%84URI"><span class="toc-number">3.2.2.</span> <span class="toc-text">自定义 Spring Security 和 安全的URI</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%96%B9%E6%B3%95%E7%BA%A7%E5%88%AB%E7%9A%84%E4%BF%9D%E6%8A%A4"><span class="toc-number">3.2.3.</span> <span class="toc-text">方法级别的保护</span></a></li></ol></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E6%80%BB%E7%BB%93"><span class="toc-number">4.</span> <span class="toc-text">总结</span></a></li></ol></div></div><div class="card-widget card-recent-post"><div class="item-headline"><i class="fas fa-history"></i><span>最新文章</span></div><div class="aside-list"><div class="aside-list-item no-cover"><div class="content"><a class="title" href="/myblog/posts/15695/" title="jenkins（1）">jenkins（1）</a><time datetime="2023-09-14T02:17:44.000Z" title="发表于 2023-09-14 10:17:44">2023-09-14</time></div></div><div class="aside-list-item no-cover"><div class="content"><a class="title" href="/myblog/posts/22012/" title="wsl的Ubuntu子系统（1）">wsl的Ubuntu子系统（1）</a><time datetime="2023-09-13T08:42:19.000Z" title="发表于 2023-09-13 16:42:19">2023-09-13</time></div></div><div class="aside-list-item no-cover"><div class="content"><a class="title" href="/myblog/posts/30302/" title="effective-java-(接口优于抽象类)">effective-java-(接口优于抽象类)</a><time datetime="2023-09-12T08:12:48.000Z" title="发表于 2023-09-12 16:12:48">2023-09-12</time></div></div><div class="aside-list-item no-cover"><div class="content"><a class="title" href="/myblog/posts/9473/" title="effective-java(创建和销毁对象--组合的形式优于继承)">effective-java(创建和销毁对象--组合的形式优于继承)</a><time datetime="2023-09-11T08:23:19.000Z" title="发表于 2023-09-11 16:23:19">2023-09-11</time></div></div><div class="aside-list-item no-cover"><div class="content"><a class="title" href="/myblog/posts/16107/" title="Hello World">Hello World</a><time datetime="2023-09-07T09:44:01.910Z" title="发表于 2023-09-07 17:44:01">2023-09-07</time></div></div></div></div></div></div></main><footer id="footer"><div id="footer-wrap"><div class="copyright">&copy;2020 - 2023 By brad 随风起舞</div><div class="framework-info"><span>框架 </span><a href="https://hexo.io">Hexo</a><span class="footer-separator">|</span><span>主题 </span><a href="https://github.com/jerryc127/hexo-theme-butterfly">Butterfly</a></div></div></footer></div><div id="rightside"><div id="rightside-config-hide"><button id="readmode" type="button" title="阅读模式"><i class="fas fa-book-open"></i></button><button id="darkmode" type="button" title="浅色和深色模式转换"><i class="fas fa-adjust"></i></button><button id="hide-aside-btn" type="button" title="单栏和双栏切换"><i class="fas fa-arrows-alt-h"></i></button></div><div id="rightside-config-show"><button id="rightside_config" type="button" title="设置"><i class="fas fa-cog fa-spin"></i></button><button class="close" id="mobile-toc-button" type="button" title="目录"><i class="fas fa-list-ul"></i></button><button id="go-up" type="button" title="回到顶部"><span class="scroll-percent"></span><i class="fas fa-arrow-up"></i></button></div></div><div><script src="/myblog/js/utils.js"></script><script src="/myblog/js/main.js"></script><script src="/myblog/js/fancybox.umd.js"></script><div class="js-pjax"></div><script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script><div id="local-search"><div class="search-dialog"><nav class="search-nav"><span class="search-dialog-title">搜索</span><span id="loading-status"></span><button class="search-close-button"><i class="fas fa-times"></i></button></nav><div class="is-center" id="loading-database"><i class="fas fa-spinner fa-pulse"></i><span>  数据库加载中</span></div><div class="search-wrap"><div id="local-search-input"><div class="local-search-box"><input class="local-search-box--input" placeholder="搜索文章" type="text"/></div></div><hr/><div class="no-result" id="local-search-results"></div><div id="local-search-stats-wrap"></div></div></div><div id="search-mask"></div><script src="/myblog/js/search/local-search.js"></script></div></div></body></html>